package com.shigi.sat.ga;

import com.shigi.knapsack.solvers.ga.KnapsackIndividual;
import com.shigi.knapsack.solvers.ga.KnapsackPopulation;
import com.shigi.sat.structure.SatInstance;

import java.util.Random;

/**
 * Created by Miroslav Šiagi on 28/01/15.
 */
public class GAUtils {

    private GAConfig gaConfig;

    public GAUtils(GAConfig gaConfig) {
        this.gaConfig = gaConfig;
    }

    public SatIndividual computeFitnessOnPopulation(SatPopulation satPopulation) {
        double bestFitness = 0.0;
        SatIndividual bestIndividual = null;

        int numOfIndividuals = satPopulation.satIndividuals.length;
        for (int i = 0; i < numOfIndividuals; i++) {
            double currentFitness = computeFitness(satPopulation.satIndividuals[i]);
            satPopulation.satIndividuals[i].fitnessValue = currentFitness;

            if(currentFitness > bestFitness) {
                bestFitness = currentFitness;
                bestIndividual = new SatIndividual(satPopulation.satIndividuals[i]);
            }
        }
        return bestIndividual;
    }

    public double computeFitness(SatIndividual satIndividual) {
        int numOfClauses = satIndividual.satInstance.satClausules.length;
        int numOfTrueClauses = 0;

        for (int i = 0; i < numOfClauses; i++) {
            numOfTrueClauses += (satIndividual.satInstance.satClausules[i].evaluateLiterals() ? 1 : 0);
        }

        satIndividual.satInstance.numberOfTrueClauses = numOfTrueClauses;

        if(satIndividual.satInstance.evaluateClauses()) {
            return (numOfTrueClauses + computeTotalWeight(satIndividual.satInstance));
        } else {
            return numOfTrueClauses;
        }

    }

    public int computeTotalWeight(SatInstance satInstance) {
        int totalWeight = 0;
        int numOfVariables = satInstance.getNumOfVariables();
        for (int i = 0; i < numOfVariables; i++) {
            totalWeight += (satInstance.variablesValues[i] ? satInstance.variablesWeights[i] : 0 );
        }
        return totalWeight;
    }

    public void setInitialValues(SatInstance satInstance) {
        int numOfVariables = satInstance.getNumOfVariables();
        for (int i = 0; i < numOfVariables; i++) {
            satInstance.variablesValues[i] = (Math.random() < gaConfig.probOfInitValue);
        }
    }

    public int performReproductionOrCrossover(SatPopulation satPopulation, SatPopulation newPopulation, int numOfNewIndivids) {
        double rndNumber = Math.random();
        if(rndNumber <= gaConfig.probOfReproduction) {
            SatIndividual reproducedIndividual = performReproduction(satPopulation);
            newPopulation.satIndividuals[numOfNewIndivids++] = reproducedIndividual;
        } else {
            SatIndividual firstParent = performReproduction(satPopulation);
            SatIndividual secondParent = performReproduction(satPopulation);
            //numOfNewIndivids = performCrossover(firstParent, secondParent, newPopulation, numOfNewIndivids);
            numOfNewIndivids = perform2PointCrossover(firstParent, secondParent, newPopulation, numOfNewIndivids);
        }
        return numOfNewIndivids;
    }

    private int performCrossover(SatIndividual firstParent, SatIndividual secondParent, SatPopulation newPopulation, int numOfNewIndivids) {
        SatIndividual copyOfFirst = new SatIndividual(firstParent);
        SatIndividual copyOfSecond = new SatIndividual(secondParent);

        Random random = new Random(System.currentTimeMillis());
        int middleIndex = random.nextInt(firstParent.satInstance.variablesValues.length + 1);

        for (int i = 0; i < middleIndex; i++) {
            boolean tmpBoolean = copyOfFirst.satInstance.variablesValues[i];
            copyOfFirst.satInstance.variablesValues[i] = copyOfSecond.satInstance.variablesValues[i];
            copyOfSecond.satInstance.variablesValues[i] = tmpBoolean;
        }

        newPopulation.satIndividuals[numOfNewIndivids++] = copyOfFirst;
        newPopulation.satIndividuals[numOfNewIndivids++] = copyOfSecond;

        return numOfNewIndivids;
    }

    private int perform2PointCrossover(SatIndividual firstParent, SatIndividual secondParent, SatPopulation newPopulation, int numOfNewIndivids) {
        SatIndividual copyOfFirst = new SatIndividual(firstParent);
        SatIndividual copyOfSecond = new SatIndividual(secondParent);
        int numOfVariables = firstParent.satInstance.variablesValues.length;

        Random random = new Random(System.currentTimeMillis());
        int firstIndex = random.nextInt(numOfVariables + 1);
        int secondIndex = random.nextInt(numOfVariables + 1 - firstIndex) + firstIndex;

        for (int i = 0; i < firstIndex; i++) {
            boolean tmpBoolean = copyOfFirst.satInstance.variablesValues[i];
            copyOfFirst.satInstance.variablesValues[i] = copyOfSecond.satInstance.variablesValues[i];
            copyOfSecond.satInstance.variablesValues[i] = tmpBoolean;
        }

        for (int i = secondIndex; i < numOfVariables; i++) {
            boolean tmpBoolean = copyOfFirst.satInstance.variablesValues[i];
            copyOfFirst.satInstance.variablesValues[i] = copyOfSecond.satInstance.variablesValues[i];
            copyOfSecond.satInstance.variablesValues[i] = tmpBoolean;
        }

        newPopulation.satIndividuals[numOfNewIndivids++] = copyOfFirst;
        newPopulation.satIndividuals[numOfNewIndivids++] = copyOfSecond;

        return numOfNewIndivids;
    }

    private SatIndividual performReproduction(SatPopulation satPopulation) {
        //return performLinearRankSelection(satPopulation);
        return performTournametnSelection(satPopulation);
    }

    private SatIndividual performLinearRankSelection(SatPopulation satPopulation) {
        double randX = Math.random();
        double cParam = gaConfig.selectPressureParam;
        double cSq = Math.pow(cParam, 2.0);
        double difference1 = cSq - 4.0*(cParam - 1.0)*randX;
        double diff1Sqrt = Math.sqrt(difference1);
        double difference2 = cParam - diff1Sqrt;
        double numberOfIndidivuals = satPopulation.satIndividuals.length;
        double ratio = (numberOfIndidivuals * difference2) / ( 2.0 * cParam - 2.0);
        int index = (int)Math.floor(ratio);

        return satPopulation.satIndividuals[index];
    }

    public static SatIndividual performTournametnSelection(SatPopulation satPopulation) {
        Random random = new Random(System.currentTimeMillis());
        int firstIndex = random.nextInt(satPopulation.satIndividuals.length);
        int secondIndex = random.nextInt(satPopulation.satIndividuals.length);
        return (satPopulation.satIndividuals[firstIndex].fitnessValue >= satPopulation.satIndividuals[secondIndex].fitnessValue) ?
                satPopulation.satIndividuals[firstIndex] : satPopulation.satIndividuals[secondIndex];
    }

    public void performMutation(SatPopulation population) {
        double probOfMutation = gaConfig.probOfMutation;
        int numOfIndividuals = population.satIndividuals.length;
        int numOfVariables = population.satIndividuals[0].satInstance.variablesValues.length;

        for (int i = 0; i < numOfIndividuals; i++) {
            for (int j = 0; j < numOfVariables; j++) {
                if(Math.random() < probOfMutation) {
                    population.satIndividuals[i].satInstance.variablesValues[j] = !population.satIndividuals[i].satInstance.variablesValues[j];
                }
            }
        }
    }
}
